﻿using System;
using System.Diagnostics;
using System.Linq;
using LambdaGp.Core;
using LambdaGp.Core.Streaming;
using LambdaGp.Operators.Functions.MathFunctions;
using LambdaGp.Operators.Terminals;

namespace LambdaGp.Demo1
{
    public class Program
    {
        public static void Main(string[] args)
        {            
            // TestLoader();
            Console.WriteLine("Select function you wish to find:");
            Console.WriteLine("1: A constant (13.5)");
            Console.WriteLine("2: A polynomial ((x * x * 5) + (x * 3) + 5)");
            Console.WriteLine("3: Sinus");
            Console.WriteLine("4: A polynomial for timing");

            ConsoleKeyInfo key = Console.ReadKey();
            Population population;
            switch (key.KeyChar)
            {
                case '1':
                    population = SetupFindConstantTest(13.5);
                    break;
                case '2':
                    population = SetupFindFunctionTest(x => (x * x * 5) + (x * 3) + 5);
                    break;
                case '3':
                    population = SetupFindFunctionTest(Math.Sin);
                    break;
                case '4':
                    // Population.SetRandomSeed(0);
                    population = SetupFindFunctionTest(x => (x * x * 2) + (x * 3) + 5);
                    population.TargetFitness = 0.005;
                    break;
                default:
                    Console.WriteLine("Unrecognized command!");
                    Console.ReadKey();
                    return;
            }
            Console.WriteLine();
            Stopwatch stopwatch = Stopwatch.StartNew();
            population.AfterGeneration +=
                (sender, e) =>
                {
                    //if (e.DifferentFromPreviousGeneration)
                    {
                        Console.WriteLine(
                            "Generation={0}, \n\rNew champion: \n\r{1}, \n\rFitness={2} (bef. parsimony {3})",
                            e.GenerationCount,
                            e.Champion.ToPrettyString(),
                            e.Champion.Fitness,
                            e.Champion.FitnessBeforeParsimony);
                    }
                    if (e.TargetFitnessReached)
                    {
                        Console.WriteLine("Reached target fitness!");
                    }
                };

            while (population.GenerationCount < population.MaxGenerations && population.TargetFitnessReached == false)
            {
                population.RunGeneration();
                if (Console.KeyAvailable)
                {
                    key = Console.ReadKey();
                    if (key.KeyChar == 'R' || key.KeyChar == 'r')
                    {
                        population.ResetPopulation();
                    }
                }
            }

            Console.WriteLine("Done, elapsed time: {0}", stopwatch.Elapsed);
            Console.ReadKey();
        }

        private static void TestLoadGenome(GenomeLoader genomeLoader, string genome)
        {
            try
            {
                OperatorNode node = genomeLoader.LoadFromString(genome);
                Console.WriteLine("In : {0}\n\r:Out: {1}", genome, node);
            }
            catch (Exception e)
            {
                Console.WriteLine("In : {0}\n\rRaised exception: {1}", genome, e);
            }
        }

        private static Population SetupFindFunctionTest(Func<double, double> func)
        {
            Population population = new Population();
            population.GenomeDefinition.MaxSize = 50;
            population.GenomeDefinition.MaxDepth = 7;
            population.GenomeDefinition.AddOperators(
                new Add(),
                new Div(),
                new Sub(),
                new Mul(),
                new Pow(),
                new Ephemeral(-5, +5));

            Variable vx = population.GenomeDefinition.AddOperator(new Variable("x", 0.1));
            population.GenomeDefinition.MaxDepth = 5;
            population.PopulationSize = 5000;
            population.ParsimonyPressure = 0.0;
            population.MaxGenerations = 50000;
            population.TargetFitness = 0.001;

            population.FitnessFunction =
            individual =>
            {
                const double range = 2.0 * Math.PI;
                const int steps = 50;
                const double stepSize = 2.0 * range / steps;

                VariableInstance x = Variable.GetVariable(individual, "x");

                int tests = 0;
                x.Value = -range;
                double errorSum = 0;

                while (x.Value < range)
                {
                    double targetValue = func(x.Value);
                    double error = targetValue - individual.Evaluate();
                    errorSum += error * error;
                    x.Value += stepSize;
                    tests++;
                }

                return errorSum / tests;
            };

            return population;
        }

        private static Population SetupFindConstantTest(double targetValue)
        {
            Population population = new Population();
            population.GenomeDefinition.AddOperators(
                new Add(),
                new Mul(),
                new Div(),
                new Sub(),
                new Constant(1),
                new Constant(3),
                new Constant(5));
            population.GenomeDefinition.MaxDepth = 3;
            population.PopulationSize = 750;

            population.FitnessFunction =
                ind =>
                Math.Abs(targetValue - ind.Evaluate());
            return population;
        }

        private static void ShowPopulation(Population pop)
        {
            Console.WriteLine(string.Format("Generation: {0}", pop.GenerationCount));
            pop
                .CurrentGeneration
                .Take(3)
                .ForEach(
                    ind =>
                        Console.WriteLine(
                            string.Format(
                                "Genome={0}, \n\tFitness={1}",
                                ind.ToPrettyString(),
                                ind.Fitness)));
        }
    }
}